/**
 * Systemprogrammierung

 * Multiplayer-Quiz
 *
 * Server
 */


#include "common/question.h"
#include "common/sockets.h"
#include "clientthread.h"
#include "sync.h"
#include "main.h"
#include "login.h"
#include "user.h"
#include "catalog.h"

/* Errechne den Punktestand */

uint32_t punkteStand(uint32_t timeLeft, uint32_t timeout) {

	uint32_t score = (timeLeft * 1000 /*UL*/) / timeout;

	/* auf 10 er - Stellen runden */
	score = ((score + 5 /*UL*/) / 10 /*UL*/);

	return score;
}

/* Warte auf Timeout oder eingehendee RFC-MSG und stoppe Zeit */


int tryRecvMSG(struct timespec* start, struct timespec* ende,
		struct timespec* rest, int socket, char* questionAnswered) {

	int ret_recv;

	ret_recv = recv(socket, questionAnswered, sizeof(questionAnswered),
			MSG_DONTWAIT);

	// Verbindung verloren
	if (ret_recv == 0) {
		return -2;
	}

	// recv-Fehler
	if (ret_recv < 0) {

		// socketet blockiert, keine weiteren Daten bereit
		if (errno== EAGAIN || errno == EWOULDBLOCK) {

			// Hole aktuelle Zeit
			clock_gettime(CLOCK_MONOTONIC, start);

			// Wenn aktuelle Zeit kleiner als endezeit, Timeout noch nicht ausgelaufen
			if (cmpTimespec(start, ende) == -1) {

				*rest = timespecSub(ende, start);
			}
			//Timeout
			else {

				return -1;
			}
		}
		// anderer Fehler beim recv
		else {

			return -2;
		}
	} else {
		return ret_recv;
	}

	return 0;
}

/* Warte auf Timeout */

int warteTimeout(struct RFCMSG* msg, uint16_t timeout, int socketFd) {

	// Filedeskriptor-Menge
	fd_set fds;

	// Variablen zur Zeitmessung
	struct timespec startzeit;
	struct timespec endezeit;
	struct timespec restzeit;

	// Rückgabewert für PSelect
	int ret_select, ret_recv = { 0 };

	// Hole aktuellen Zeitwert in Startzeit
	clock_gettime(CLOCK_MONOTONIC, &startzeit);

	// Setze endezeit auf
	endezeit.tv_nsec = startzeit.tv_nsec;
	endezeit.tv_sec = startzeit.tv_sec + timeout;

	// Restzeit anfangs auf Länge von Timeout setzen
	restzeit.tv_nsec = 0;
	restzeit.tv_sec = timeout;

	char answer_msg[4] = { 0 };

	while (ret_recv == 0) {
		// Nehme zu überwachendeen socketet in Filedeskriptormenge
		FD_ZERO(&fds);
		FD_SET(socketFd, &fds);

		debugPrint("socketet vor pselect: %d\n", socketFd);

		// Warten auf Ablauf der Restzeit oder Daten, je nachdem was schneller eintritt
		ret_select = pselect(socketFd + 1, &fds, NULL, NULL, &restzeit, NULL);

		debugPrint("socketet nach pselect: %d", socketFd);
		debugPrint("ret_select: %d", ret_select);
		debugPrint("fd_isset: %d\n", FD_ISSET(socketFd, &fds));

		//TODO Try fd_isset zum überprüfen ob fd wirklich lesebereit/auslöser von pselect

				if (ret_select == 0) {
					return -1;
				} else if (ret_select == -1) {
					errorPrint("pselect Error\n");
					return -2;
				} else {

					ret_recv = tryRecvMSG(&startzeit, &endezeit, &restzeit, socketFd, answer_msg);
			debugPrint("recv after try: %d", ret_recv);
		}
	}

		if (ret_recv> 0) {
			ret_recv = recv(socketFd, answer_msg + ret_recv, sizeof(answer_msg) - ret_recv, MSG_WAITALL);
		}

		//TODO Fehler abfangen

		memcpy(&msg->header.type, answer_msg, 1);
		memcpy(&msg->header.length, answer_msg + 1, 2);
		msg->header.length = ntohs(msg->header.length);
		msg->data = (char*) malloc(msg->header.length);
		memcpy(msg->data, answer_msg + 3, msg->header.length);

		debugPrint("msg data:");
		debugPrint("ret_recv: %d", ret_recv);
		debugPrint("type: %d", msg->header.type);
		debugPrint("len: %d", msg->header.length);
		uint8_t answer;
		memcpy(&answer, msg->data, 1);
		debugPrint("data: %d", answer);

		clock_gettime(CLOCK_MONOTONIC, &startzeit);

		// Wenn aktuelle Zeit kleiner als endezeit, Timeout noch nicht ausgelaufen
		if (cmpTimespec(&startzeit, &endezeit) == -1) {

			restzeit = timespecSub(&endezeit, &startzeit);
		}
		//Timeout
		else {

			return -1;
		}

		uint32_t timeleft = 0;
		timeleft = restzeit.tv_nsec / 1000000;
		timeleft = timeleft + (restzeit.tv_sec * 1000);

		return timeleft;
	}

char* actualCat = NULL; /**< Globale Variable für aktuelle gewählten Katalog */

/**	\brief Setzen des Aktuellen Kataloges
 *	\param kat Katalogname
 *	\param len Laenge des Katalognamen*/


void setActualCat(char* kat, int len) {

	if (actualCat == NULL) {
		actualCat = (char*) malloc(len + 1);
	} else {
		free(actualCat);
		actualCat = NULL;
		actualCat = (char*) malloc(len + 1);
	}

	memset(actualCat, 0, len + 1);
	memcpy(actualCat, kat, len);

	debugPrint("aktuell gewählter Katalog: %s", actualCat);

	return;
}

/**	\brief Sich den Aktuellen Katalog holen */
char* getActualCat() {
	return actualCat;
}

/**	\brief Frage aus SharedMem an Client sendeen, löst pselect aus und wertet Antwort aus
 *	\param anzahlQuestion Index der Frage
 *	\param clientID ID des Client, damit an richtigen Client gesendeet wird*/

bool sendeQuestion(int* anzahlQuestion, uint8_t clientID) {

	//Variablen zur Frageverwaltung
	struct RFCMSG clientMSG;

	Question* question = NULL;

	QuestionMessage questionMSG;

	uint8_t answer = 0;

	uint8_t correct_answer = 4;

	userdata_mutex_lock();

	int socket = getClientSocket(clientID);

	userdata_mutex_unlock();

	int timeLeft = 0;

	// Frage aus Katalog laden
	question = getQuestion(anzahlQuestion);

	// Keine Fragen mehr -> Spieler fertig -> sendee leere Question
	if (question == NULL) {

		catchError(sendeMSG(socket, QUESTION, 0, NULL));

		debugPrint("Spieler %d ist fertig!\n", clientID);

		int sem_check;

		if (sem_trywait(&game_over_sem) == -1) {

			if (errno== EAGAIN) {

				sem_getvalue(&game_over_sem, &sem_check);

				if (sem_check != 0) {

					sem_wait(&game_over_sem);
				}
			}
		}

		sem_getvalue(&game_over_sem, &sem_check);

		if (sem_check == 0) {
			return true;
		}

		sem_wait(&quit_game_sem);

		pthread_exit(NULL);

	} else {

		//Frage verpacken und sendeen
		strcpy(questionMSG.question, question->question);

		int i;
		for (i = 0; i < NUM_ANSWERS; ++i) {
			strcpy(questionMSG.answers[i], question->answers[i]);
		}

		questionMSG.timeout = question->timeout;

		questionMSG.timeout = htons(questionMSG.timeout);

		correct_answer = (uint8_t) question->correct;

		if (catchError(sendeMSG(socket, QUESTION, sizeof(questionMSG),
				(void*) &questionMSG)) == -1) {
			return true;
		}

		*anzahlQuestion = *anzahlQuestion + 1;
	}

	// Löse Pselect aus - warte auf Timeout..
	timeLeft = warteTimeout(&clientMSG, question->timeout, socket);

	uint8_t result[2] = { 0 };

	memset(&result[1], correct_answer, 1);

	if (timeLeft == -1) {

		memset(&result[0], 1, 1);

		if (catchError(sendeMSG(socket, QUESTION_RESULT, sizeof(result),
				(void*) result)) == -1) {
			return true;
		}

		return false;
	}

	if (timeLeft == -2) {
		if (catchError(sende_err_MSG(socket, WARNING,
				"Interner Server Fehler beim Verarbeiten der Frage")) == -1) {
			return true;
		}
		return false;
	}

	if (timeLeft >= 0) {

		// Wenn QUESTION_ANSWERED Nachricht empfangen wurde
		if (clientMSG.header.type == QUESTION_ANSWERED) {

			memcpy(&answer, clientMSG.data, sizeof(uint8_t));

			if (answer == correct_answer) {

				userdata_mutex_lock();

				set_client_score(clientID, score_for_timeleft(
						(uint32_t) timeLeft, (uint32_t) questionMSG.timeout));

				userdata_mutex_unlock();

				sem_post(&score_agent_sem);
			}

			memset(&result[0], 0, 1);

			if (catchError(sendeMSG(socket, QUESTION_RESULT, sizeof(result),
					(void*) result)) == -1) {
				return true;
			}
		}

		return false;

	} else {
		// Keine QUESTION_ANSWERED empfangen
		return false;
	}
}

/**	\brief Prüfe Datei-endeung auf .cat
 *	\param catalog Katalogname*/

int valid_cat(char* catalog) {

	char* dot_ptr = strrchr(catalog, '.');

	if (dot_ptr == NULL) {

		return -1;
	}

	char* type_has_to = { "cat" };

	if (strstr(dot_ptr + 1, type_has_to) != NULL) {

		return 0;
	}

	return -1;
}

char** catalogs = NULL; /**< Globale Variable catalogs */

/**	\brief Katalogliste setzen
 *	\param cats Liste der Kataloge*/
void set_catalogs(char** cats) {
	catalogs = cats;
}

/* Katalogliste an Client sendeen */

bool sende_catalogs(int clientID) {

	int client_socket = get_client_socketet(clientID);

	int i = 0;

	while (catalogs[i + 1] != '\0') {

		if (valid_cat(catalogs[i]) == 0) {

			// Methode zur Fehlerbehandlung beim Sendeen
			if (catchError(sendeMSG(client_socket, CATALOG_RESPONSE, strlen(
					catalogs[i]), catalogs[i])) == -1) {
				return true;
			}
		}
		i++;
	}

	// Methode zur Fehlerbehandlung beim Sendeen
	catchError(sendeMSG(client_socket, CATALOG_RESPONSE, 0, NULL));

	char* kat = get_actualCat();

	if (kat != NULL) {

		// Methode zur Fehlerbehandlung beim Sendeen
		if (catchError(sendeMSG(client_socket, CATALOG_CHANGE, strlen(kat),
				kat)) == -1) {
			return true;
		}

	}

	return false;
}

/* geänderte Katalogauswahl an Clients weiterleiten */


void catalog_change_msg(struct RFCMSG* clientMSG) {

	set_actualCat((char*) clientMSG->data, clientMSG->header.length);

	int* client_slots = get_client_slots();

	int i;

	for (i = 1; i < MAX_USERS; i++) {

		if (client_slots[i] == 1) {

			int client_socket = get_client_socketet(i);

			// Methode zur Fehlerbehandlung beim Sendeen
			if (catchError(sendeMSG(client_socket, CATALOG_CHANGE,
					clientMSG->header.length, clientMSG->data)) == -1) {
				//TODO Methode um bestimmten Client-Thread zu beendeen und Client aus Userverwaltung zu streichen
			}
		}
	}

	return;
}

/* Methode für Spielstart durch Spielleiter */


bool launch_game(int socket, int clientID, struct RFCMSG* clientMSG) {

	//Wenn Client nicht Spielleiter ist
	if (clientID != 0) {

		debugPrint("START_GAME Message fehlerhaft\n");

		// Methode zur Fehlerbehandlung beim Sendeen
		if (catchError(sende_err_MSG(socket, WARNING,
				"START_GAME Message fehlerhaft..")) == -1) {
			return true;
		}

		return false;
	}

	// Laden des ausgewählten Kataloges
	int questions = 0;

	// Erwarte OK mit Anzahl der Kataloge
	char* catalog = getActualCat();
	char err_msg[50];
	questions = ladeLoader(catalog, err_msg);

	if (questions == -1) {

		debugPrint("Fehler beim Katalog laden: %s", err_msg);

		// Methode zur Fehlerbehandlung beim Sendeen
		if (catchError(sende_err_MSG(socket, WARNING, err_msg)) == -1) {
			return true;
		}

		return false;
	}

	if (oeffneSharedMem(questions) != 0) {
		return false;
	}

	// Setze Status des Login-Thread, damit Login-Anfragen bei laufendeem Spiel entsprechende behandelt werden
	status_mutex_lock();

	change_game_status(GAME_RUNNING);

	status_mutex_unlock();

	// Sendee leere START_GAME an alle Clients
	int i;
	int* client_slots = get_client_slots();
	for (i = 0; i < MAX_USERS; i++) {
		if (client_slots[i] == 1) {
			int client_socket = get_client_socketet(i);

			// Methode zur Fehlerbehandlung beim Sendeen
			if (catchError(sendeMSG(client_socket, START_GAME, 0, NULL)) == -1) {
				//TODO Methode um bestimmten Client-Thread zu beendeen und Client aus Userverwaltung zu streichen
			}
		}
	}

	// Initialisiere Game-Over Semaphor für Spielendee mit Anzahl der Spieler
	sem_init(&game_over_sem, 0, get_client_count());

	sem_post(&score_agent_sem);

	infoPrint("Starte Spiel\n");

	return false;
}

/**	\brief Holt Client-ID in Reihenfolge aus Userliste und sendeet Game Over Message mit Platzierung */

void game_over() {

	uint8_t clientID[MAX_USERS] = { 0 };

	uint8_t rank = 1;

	userdata_mutex_lock();

	int clients = get_ranking(clientID);

	// Schicke Rank an alle Clients
	int i, j = 0;

	for (i = 0; i < clients; i++) {

		int client_socket = get_client_socketet(clientID[i]);

		debugPrint("clientID: %d\nclient_socket: %d\n", clientID[i],
				client_socket);

		if (connection_check(client_socket) == 0) {

			catchError(sendeMSG(client_socket, GAME_OVER, sizeof(rank),
					(void*) (&rank)));

			j++;

		}

		rank++;
	}

	userdata_mutex_unlock();

	for (i = 0; i < j; i++) {

		sem_post(&quit_game_sem);
	}
}

/**	\brief Methode für Spielleiter Left */

void spielleiter_exit() {

	// Benötige Client-Slots um alle noch angemeldeten Clients zu benachrichtigen
	int* client_slots = get_client_slots();

	// Schicke ERROR an alle Clients: Spielleiter hat Spiel verlassen
	int i;
	for (i = 0; i < MAX_USERS; i++) {
		if (client_slots[i] == 1) {
			int client_socket = get_client_socketet(i);

			// Methode zur Fehlerbehandlung bei Sendeen/Empfangen
			sende_err_MSG(client_socket, FATAL,
					"Der Spielleiter hat das Spiel verlassen, Server wird beendeet");
		}
	}
}

/**	\brief Methode für zuwenige Spieler in Spielphase */

void only_one() {

	// Benötige Client-Slots um alle noch angemeldeten Clients zu benachrichtigen
	int* client_slots = get_client_slots();

	// Schicke ERROR an alle Clients: Spielleiter hat Spiel verlassen
	int i;
	for (i = 0; i < MAX_USERS; i++) {
		if (client_slots[i] == 1) {
			int client_socket = get_client_socketet(i);

			// Methode zur Fehlerbehandlung beim Sendeen
			sende_err_MSG(client_socket, FATAL,
					"Zuwenige Spieler um Spiel fortzuführen.");
		}
	}
}

uint8_t id_to_server; /**< Globale Variable für clientID */

/**	\brief Setze Globale ID
 *	\param id ID des Client*/
void set_global_id(uint8_t id) {
	id_to_server = id;
}

/**	\brief Hole die Globale ID */
int get_clientID() {
	return id_to_server;
}

/**	\brief Prüfe ob Bedingungen für Spielendee erfüllt und leite ggf. Serverendee ein
 * 	\param para Rückgabeparameter des Thread
 *	\param clientID ID des Client */
void check_gameover(void* para, int clientID) {

	/* ** ** ** Alle Spieler fertig? (Game-Over-Sem == 0) ** ** ** */

	int check_game_over = MAX_USERS;

	status_mutex_lock();

	int status = get_game_status();

	status_mutex_unlock();

	if (status == GAME_RUNNING) {

		sem_getvalue(&game_over_sem, &check_game_over);

		if (check_game_over == 0) {

			infoPrint("Game Over\n");

			// Setze globale clientID für Server, damit dieser weiss, auf welchen Client_Thread er warten muss.
			set_global_id(clientID);

			// Setze Semaphor für main-Methode um Server-endee einzuleiten
			sem_post(&exit_sem);

			para = (void*) GAME_OVER;

			// Thread endee
			pthread_exit(para);
		}
	}
}



void* clienThread(void* param) {

	/* Hole Startparameter */

	uint8_t clientID = 0;

	memcpy(&clientID, param, sizeof(uint8_t));

	debugPrint("Client-Thread von Client %d läuft\n", clientID);

	// Wenn quit = true, beendee Client-Thread
	bool quit = false;

	/* Hole socketet von Client-ID */

	// setze Userdaten Mutex
	userdata_mutex_lock();

	// hole socketet aus Userdaten
	int socket = get_client_socketet(clientID);
	if (socket == -1) {
		debugPrint("Client ID konnte nicht in Userdaten gefunden werden");
		userdata_mutex_unlock();
		pthread_exit("Userdaten Fehler");
	}

	// gib Mutex frei
	userdata_mutex_unlock();

	/*  Empfange Nachricht von Client  */

	// MSG-Struktur für Nachrichtenempfang
	struct RFCMSG clientMSG;

	// Question-Zähler für Fortschritt
	int anzahlQuestion = 0;

	// Spielstatus

	status_mutex_lock();

	int status = get_game_status();

	status_mutex_unlock();

	while (quit == false) {

		/*  Reaktion auf Nachrichtentyp  */

		switch (catchError(receiveMSG(socket, &clientMSG))) {

		// Wenn CATALOG_REQUEST Nachricht empfangen wurde
		case CATALOG_REQUEST:

			userdata_mutex_lock();

			quit = sende_catalogs(clientID);

			userdata_mutex_unlock();

			break;

			// Wenn CATALOG_CHANGE Nachricht empfangen wurde
		case CATALOG_CHANGE:

			userdata_mutex_lock();

			catalog_change_msg(&clientMSG);

			userdata_mutex_unlock();

			break;

			// Wenn START_GAME Nachricht empfangen wurde
		case START_GAME:

			userdata_mutex_lock();

			if (get_client_count() >= 2) {

				quit = launch_game(socket, clientID, &clientMSG);

				userdata_mutex_unlock();

			} else {

				userdata_mutex_unlock();

				if (catchError(sende_err_MSG(socket, WARNING,
						"Zuwenige Spieler für Spielstart")) == -1) {
					quit = true;
				}
			}

			break;

			// Wenn QUESTION_REQUEST Nachricht empfangen wurde
		case QUESTION_REQUEST:

			quit = sende_question(&anzahlQuestion, clientID);

			break;

			/* Fehler beim Empfangen abfangen  */

		case COM_ERR:

			debugPrint("Client-Thread: Verbindung zu Client %d verloren\n",
					clientID);

			quit = true;

			break;

		default:
			break;
		}

		/*  Prüfen ob beim Empfangen einer Nachricht Speicher allokiert wurde */

		if ((clientMSG.header.type != 0) && (clientMSG.header.length != 0)) {
			// Freigeben des dynamischen Speichers (socketets. c - receiveMSG())
			free(clientMSG.data);
			clientMSG.data = NULL;
		}
	}

	/*  Soll Client-Thread geschlossen werden?  */

	if (quit == true) {

		int who_left = 0;

		status_mutex_lock();

		status = get_game_status();

		status_mutex_unlock();

		if (status == GAME_RUNNING) {

			// Game-Over Semaphor - 1
			if (sem_trywait(&game_over_sem) == -1) {
				if (errno== EAGAIN) {
					int sem_check;
					sem_getvalue(&game_over_sem, &sem_check);
					if (sem_check != 0) {
						sem_wait(&game_over_sem);
					}
				}
			}

			/* Alle Spieler fertig?  */

			check_gameover(param, clientID);

			// Userdaten Mutex holen
			userdata_mutex_lock();

			// Spieler aus User-Liste entfernen
			who_left = remove_user(socket);

			// Gib Userdaten-Mutex frei
			userdata_mutex_unlock();

			// Prüfe ob noch mindestens 2 Spieler im Spiel
			if (get_client_count() < 2) {

				infoPrint("Zuwenige Spieler um Spiel fortzusetzen\n");

				// Setze Rückgabewert auf Fehler-Code (Benutze param, weil nach Thread-endee lokale Variablen nicht mehr existieren)
				who_left = ONLY_ONE;

				// Setze globale clientID für Server, damit dieser weiss, auf welchen Client_Thread er warten muss.
				set_global_id(clientID);

				// Setze Semaphor für main-Methode um Server-endee einzuleiten
				sem_post(&exit_sem);

				param = (void*) who_left;

				// Thread endee
				pthread_exit(param);
			}

		} else {

			// Userdaten Mutex holen
			userdata_mutex_lock();

			// Spieler aus User-Liste entfernen
			who_left = remove_user(socket);

			// Gib Userdaten-Mutex frei
			userdata_mutex_unlock();
		}

		infoPrint("Schliesse Client-Thread von Client %d\n", clientID);

		if (who_left == SPIELLEITER_LEFT && status != GAME_RUNNING) {
			// Setze Semaphor für main-Methode um Server-endee einzuleiten
			sem_post(&exit_sem);
		}

		// Setze Rückgabewert auf Fehler-Code (Benutze param, weil nach Thread-endee lokale Variablen nicht mehr existieren)
		param = (void*) who_left;

		// Thread ende
		pthread_exit(param);
	}
	return NULL;
}

